🔬 多关系数据查询 — 上机实验

📚 数据库原理与应用 📖 第6章 6.2节 ⏱️ 预计 45 分钟
🎯 实验目标
  • 能够独立编写内连接(INNER JOIN)查询语句
  • 能够使用左外连接(LEFT JOIN)保留主表中未匹配的记录
  • 能够编写自连接查询,解决同一表内的比较问题
  • 能够在多表查询中正确使用表前缀避免二义性错误

🛠️ 环境准备

请打开 MySQL 客户端,先创建实验数据库并导入以下测试数据。复制下方代码,一键粘贴运行即可。

⚠️ 运行前请确认
如果你之前已经建过同名数据库 teaching_db,运行下面的代码会先删除旧库再重建。请确认不会影响你的其他数据。
SQL — 建库建表 + 插入数据
-- ==============================
-- 第6章 多关系数据查询 实验数据
-- ==============================

DROP DATABASE IF EXISTS teaching_db;
CREATE DATABASE teaching_db DEFAULT CHARSET utf8mb4;
USE teaching_db;

-- 学生表 s
CREATE TABLE s (
  sno  CHAR(4) PRIMARY KEY,
  sn   VARCHAR(20),
  sex  CHAR(2),
  age  INT,
  maj  VARCHAR(30)
);

INSERT INTO s VALUES
('s1', '赵亦',  '女', 20, '计算机'),
('s2', '钱尔',  '男', 21, '信息管理'),
('s3', '孙珊',  '女', 19, '计算机'),
('s4', '李思',  '男', 22, '信息管理'),
('s5', '周武',  '男', 20, '计算机'),
('s6', '王彤',  '女', 19, '计算机');

-- 课程表 c
CREATE TABLE c (
  cno  CHAR(4) PRIMARY KEY,
  cn   VARCHAR(30),
  ct   INT
);

INSERT INTO c VALUES
('c1', '数据库',        64),
('c2', '数学',          72),
('c3', '操作系统',      56),
('c4', '程序设计基础',  48),
('c5', '数据结构',      60);

-- 教师表 t
CREATE TABLE t (
  tno  CHAR(4) PRIMARY KEY,
  tn   VARCHAR(20),
  sex  CHAR(2),
  prof VARCHAR(20)
);

INSERT INTO t VALUES
('t1', '李力',  '男', '教授'),
('t2', '王平',  '女', '副教授'),
('t3', '刘伟',  '男', '讲师'),
('t4', '张雪',  '女', '助教'),
('t5', '陈波',  '男', '讲师'),
('t6', '张刚',  '男', '教授');

-- 授课表 tc
CREATE TABLE tc (
  tno  CHAR(4),
  cno  CHAR(4),
  PRIMARY KEY (tno, cno)
);

INSERT INTO tc VALUES
('t1', 'c1'),
('t2', 'c2'),
('t3', 'c3'),
('t3', 'c5'),
('t4', 'c4'),
('t5', 'c1');

-- 选课表 sc
CREATE TABLE sc (
  sno   CHAR(4),
  cno   CHAR(4),
  score DECIMAL(5,1),
  PRIMARY KEY (sno, cno)
);

INSERT INTO sc VALUES
('s1', 'c1', 90),
('s1', 'c2', 85),
('s1', 'c3', 88),
('s2', 'c1', 72),
('s2', 'c2', 68),
('s3', 'c1', 95),
('s3', 'c3', 78),
('s4', 'c2', 82),
('s4', 'c4', 60),
('s5', 'c1', 76),
('s5', 'c4', 55);

-- 验证数据
SELECT '学生表' AS 表名, COUNT(*) AS 记录数 FROM s
UNION ALL
SELECT '课程表', COUNT(*) FROM c
UNION ALL
SELECT '教师表', COUNT(*) FROM t
UNION ALL
SELECT '授课表', COUNT(*) FROM tc
UNION ALL
SELECT '选课表', COUNT(*) FROM sc;
▶ 验证结果应为
表名记录数
学生表6
课程表5
教师表6
授课表6
选课表11

数据表结构速览

了解表之间的关联关系,后续任务会用到。

💡 关联关系
学生表 s(sno)← 选课表 sc(sno, cno)→ 课程表 c(cno)
教师表 t(tno)← 授课表 tc(tno, cno)→ 课程表 c(cno)
其中 s6(王彤)没有选课记录t6(张刚)没有授课记录——这对练习外连接很关键。

⭐ 基础任务

任务 1 ⭐ 基础

查询学号为 's5' 的学生的选课信息,列出学号、姓名和课程号
(提示:需要连接学生表 s 和选课表 sc)

🎯 应返回 2 行数据,包含 sno、sn、cno 三列。
💡 查看语法提示
参考语法:SELECT 表1.字段, ... FROM 表1 INNER JOIN 表2 ON 表1.共同列 = 表2.共同列 WHERE 条件;
🔑 查看参考答案
SQL
SELECT s.sno, sn, cno
FROM s INNER JOIN sc
  ON s.sno = sc.sno
WHERE s.sno = 's5';

sno 在两张表中都有,必须用 s.sno 加表前缀区分。

任务 2 ⭐ 基础

查询所有授课教师的教师号、姓名和课程名,按教师号升序排列。
(提示:需要连接三张表——教师表 t、授课表 tc、课程表 c)

🎯 应返回 6 行数据(注意 t6 张刚没有授课记录,不会出现在内连接结果中)。
💡 查看语法提示
三表连接:FROM t INNER JOIN tc INNER JOIN c ON t.tno = tc.tno AND tc.cno = c.cno
🔑 查看参考答案
SQL
SELECT t.tno, tn, cn
FROM t INNER JOIN tc INNER JOIN c
  ON t.tno = tc.tno AND tc.cno = c.cno
ORDER BY tno;
任务 3 ⭐ 基础

查询所有教师的教师号、姓名和授课课程名(没有授课的教师也要显示,课程名显示为 NULL)。按教师号升序排列。
(提示:使用 LEFT JOIN)

🎯 应返回 7 行数据(比任务 2 多出张刚 t6,课程名为 NULL)。
💡 查看语法提示
左外连接语法:FROM t LEFT OUTER JOIN tc ON ... LEFT OUTER JOIN c ON ...
OUTER 关键字可省略。
🔑 查看参考答案
SQL
SELECT t.tno, tn, cn
FROM t LEFT JOIN tc ON t.tno = tc.tno
     LEFT JOIN c ON tc.cno = c.cno
ORDER BY tno;

⭐⭐ 提高任务

任务 4 ⭐⭐ 提高

查询选课人数在 3 人及以上的课程号、课程名和选课人数。

🎯 应返回 1-2 行数据(具体取决于哪些课程选课达到 3 人)。
🔑 查看参考答案
SQL
SELECT c.cno, cn, COUNT(sc.sno) AS 选课人数
FROM c INNER JOIN sc
  ON c.cno = sc.cno
GROUP BY c.cno, cn
HAVING COUNT(sc.sno) >= 3;

关键:先 INNER JOIN 连接课程表和选课表 → GROUP BY 分组 → HAVING 过滤。注意 HAVING 中要重写 COUNT 表达式,不能直接用别名"选课人数"(MySQL 允许,但 SQL 标准不允许)。

任务 5 ⭐⭐ 提高

查询所有学生的学号、姓名、课程号和成绩(没有选课的学生也要显示)。请分别用 LEFT JOIN 和 RIGHT JOIN 两种方式实现。

🎯 两种写法结果应相同,应返回 12 行(包含王彤 s6 的 NULL 记录)。
🔑 查看参考答案
SQL — LEFT JOIN 写法
SELECT s.sno, sn, cno, score
FROM s LEFT JOIN sc
  ON s.sno = sc.sno;
SQL — RIGHT JOIN 等价写法
SELECT s.sno, sn, cno, score
FROM sc RIGHT JOIN s
  ON s.sno = sc.sno;

交换两张表的位置后,LEFT 变 RIGHT,主表不变,结果相同。

任务 6 ⭐⭐ 提高

使用自连接查询比"程序设计基础"课时高的课程号、课程名和课时。

🎯 应返回课时大于 48 的课程(数据库 64、数学 72、操作系统 56、数据结构 60)。
🔑 查看参考答案
SQL
SELECT x.cno AS 课程号, x.cn AS 课程名, x.ct AS 课时
FROM c AS x INNER JOIN c AS y
  ON x.ct > y.ct AND y.cn = '程序设计基础';

y 锁定"程序设计基础"的课时(48),x 遍历所有课程找出课时 > 48 的行。

⭐⭐⭐ 拓展任务

任务 7 ⭐⭐⭐ 拓展

查询与学生"王彤"专业相同的所有学生的学号和姓名(结果中不包含王彤本人)。
思考:如何用自连接实现?如何排除本人?

🎯 王彤的专业是"计算机",应返回 s1 赵亦、s3 孙珊、s5 周武(不含 s6 王彤)。
🔑 查看参考答案
SQL
SELECT x.sno, x.sn
FROM s AS x INNER JOIN s AS y
  ON x.maj = y.maj
  AND y.sn = '王彤'
  AND x.sn != '王彤';

关键在最后一个条件 x.sn != '王彤',排除了本人。也可用 x.sno != y.sno 来排除。

任务 8 ⭐⭐⭐ 拓展

查询每位学生的学号、姓名、选修课程名和成绩。没有选课的学生也要显示,课程名和成绩为 NULL。结果按学号升序排列。
(提示:需要三表连接 + 外连接)

🎯 应返回 12 行,s6 王彤的课程名和成绩为 NULL。
🔑 查看参考答案
SQL
SELECT s.sno, sn, cn, score
FROM s
  LEFT JOIN sc ON s.sno = sc.sno
  LEFT JOIN c  ON sc.cno = c.cno
ORDER BY s.sno;

两个 LEFT JOIN 都以学生表 s 为主表,确保即使没选课的 s6 也能出现。如果第二个连接用 INNER JOIN,当 sc.cno 为 NULL 时,课程表不会匹配,s6 那行会被丢弃。

✅ 实验完成检查

勾选你已完成的任务,追踪实验进度:

完成进度:0 / 9